/*
 *  ClientServer.cpp
 *  initiativeTracker
 *
 *  Created by Sam Jaffe on 5/10/12.
 *  Copyright 2012 WPI. All rights reserved.
 *
 */

#include <cerrno>
#include <cstdio>
#include <iostream>
#include <thread>
#include "ClientServer.h"

#define LISTEN_BACKLOG 50
#define address_size sizeof(struct sockaddr_in)
#define XML_READ_BUFFER_SIZE 1024

namespace {
    const std::string connectRequest("connectRequest");
} 
    
std::string Access::extract(int fd, std::string terminator) {
    return "";
}

bool Access::sendMessage(std::string message) {
    return write(access_socket, message.c_str(), message.size()) != -1;
}

#pragma mark Server class methods

template<class D>
Server<D>::Server() : Server(defaultPort) {};

template<class D>
Server<D>::Server(const unsigned short port) : serverPort(port) {
}

template<class D>
Server<D>::Server(const std::string schema) 
: Server(defaultPort, schema) {};

template<class D>
Server<D>::Server(const unsigned short port, const std::string schema)
: Server(port) {request_handler = new Server_XIO<D>(schema);}

template<class D>
void Server<D>::initiateSocket() {
    server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket == -1){
        std::cerr << "Error Opening Socket: " <<
        strerror(errno) << "(" << errno << ")" << std::endl;
        exit(-1);
    }
    
    struct sockaddr_in address;
    memset(&address, address_size);
    
    address.sin_family = AF_INET;
//    fgets(address.sin_addr, 15, popen("curl -s http://checkip.dyndns.org | sed 's/[a-zA-Z/<> :]//g'", "r"));
    address.sin_addr.s_addr = htonl (INADDR_ANY);
    address.sin_port = serverPort;
    
    if (bind(server_socket, (struct sockaddr *) &address, address_size) == -1) {
        std::cerr << "Error Binding Socket: " <<
        strerror(errno) << "(" << errno << ")" << std::endl;
        exit(-1);
    }
    
    if (listen(server_socket, LISTEN_BACKLOG) == -1) {
        std::cerr << "Error Listening: " <<
        strerror(errno) << "(" << errno << ")" << std::endl;
        exit(-1);
    }
    
    state = true;
}

template<class D>
void Server<D>::listenforClient() {
    struct sockaddr_in address;

    while (state) {  
        memset(&address, address_size);
        int client_socket = accept(server_socket, (struct sockaddr *) &address, address_size);
        
        std::thread t((new Client_Thread<D>(this, client_socket))->run);
        t.join();
    }
    shutdown();
}

template<class D>
void Server<D>::register_client(const std::string idn, 
                                const Client_Thread<D>* client) {
    clients.emplace(idn, client);
}

template<class D>
void Server<D>::shutdown() {
    if (server_socket != -1) {
        close(server_socket);
        server_socket = -1;
        state = false;
    }
}

template<class D>
void Server<D>::unreg_client(const std::string idn) {clients.erase(idn);}

template<class D>
void Server<D>::unreg_tracker(const std::string idn) {events.erase(idn);}

template<class D>
void Server<D>::unreg_member(const std::string idn) {members.erase(idn);}

template<class D>
void Server<D>::unreg_event(const std::string idn) {
    unreg_tracker(idn);
    unreg_member(idn);
}

template<class D>
std::shared_ptr<InitTracker> Server<D>::tracker(const std::string idn) const {
    return events.find(idn)->second;
}

template<class D>
std::shared_ptr<Client_Thread<D>> Server<D>::client(const std::string idn) const {
    return clients.find(idn)->second;
}

template<class D>
std::vector<std::string> Server<D>::member_ids(const std::string idn)
const {
    return members.find(idn)->second;
}

template<class D>
std::vector<std::shared_ptr<Client_Thread<D>>> Server<D>::map_members(
                                std::vector<std::string>& ids) const {
    std::vector<Client_Thread<D>*> ret;
    std::vector<std::string>::iterator it;
    for (it = ids.begin(); it < ids.end(); it++) {
        auto& temp = clients.find(*it);
        if (temp == 0)
            ids.erase(it);
        else
            ret.push_back(temp->second);
    }
    return ret;
}

template <class D>
std::vector<std::shared_ptr<Client_Thread<D>>> Server<D>::member_list(const std::string idn) const {
    return map_members(member_ids(idn));
}

#pragma mark Client_Thread class methods

template<class D>
void Client_Thread<D>::run() {
    char** buffer = (char *) malloc(XML_READ_BUFFER_SIZE);
    std::string response;
    
    read(socket, buffer, XML_READ_BUFFER_SIZE);
    
    Message m(buffer);
    xercesc::DOMNode* child = m.contents()->getFirstChild();
    if (!((char*) child->getLocalName() == connectRequest)) return;
    
    struct sockaddr_in address;
    getpeername(socket, &address, address_size);
    std::cout << "Client " << address.sin_addr << " has connected on port " << address.sin_port << std::endl;
    
    std::string xml_string(Message::responseHeader(m.id()) + "<connectResponse id='" + idn + "' /></response>");
    
    if (write(socket, xml_string.c_str(), xml_string.size()) == -1) {
        std::cout << "Unable to respond to connect request from Client: " << strerror(errno) << "(" << errno << ")" << std::endl;
        return;
    }
    
    while ((xml_string = extract(socket, "</request>")).compare("")) {
        response = host->request_handler->process(host, this, xml_string);
        if (!sendMessage(response)) break;
    }
    
    close();
}

template<class D>
void Client_Thread<D>::close() {
    std::cout << disconnect << std::endl;
    host->unreg_client(idn);
    close(access_socket);
}

template<class D>
void Client_Thread<D>::getId() {return idn;}

#pragma mark Client class methods

Client::Client() : init(new InitTracker()) {}

bool Client::setAccess(Server_Access* sac) {
    access = sac;
}

#pragma mark Server_Access class methods

Server_Access::Server_Access(Client* cl, const std::string host) : Server_Access(cl, host, defaultPort) {}

Server_Access::Server_Access(Client* cl, const std::string h, const unsigned short p) : host(h), port(p), client(cl), response_handler(new Client_XIO(schema)), Access(socket(AF_INET, SOCK_STREAM, 0)) {
    client->setAccess(this);
}

Server_Access::Server_Access(Client* cl, const std::string host, std::string schema) : Server_Access(cl, host, defaultPort, schema) {}

Server_Access::Server_Access(Client* cl, const std::string h, const unsigned short p, std::string schema) : host(h), port(p), client(cl), response_handler(new Client_XIO(schema)), Access(socket(AF_INET, SOCK_STREAM, 0)) {}

Server_Access::~Server_Access() {disconnect();}

bool Server_Access::connect() {
    if (access_socket == -1) {
        std::cerr << "Unable to connect to server: " << strerror(errno) << "(" << errno << ")" << std::endl;
        return false;
    }
    active = true;
    std::thread t(std::bind(&Server_Access::run, this));
    t.join();
    return true;
}

void Server_Access::run() {
    while (active) {
        try {
            std::string xml_string = extract(access_socket, "</response>");
            if (!xml_string.compare("")) {
                disconnect();
                break;
            }
            
            response_handler->process(client->event(), std::shared_ptr<Server_Access>(this), xml_string);
        } catch (int error) {
            std::cerr << "Error in processing stream: " << strerror(errno) << "(" << errno << ")" << std::endl;
            active = false;
        }
    }
}

void Server_Access::disconnect() {
    if (!active) return;
    active = false;
    
    if (close(access_socket) == -1)
        std::cerr << "Unable to disconnect from server: " << strerror(errno) << "(" << errno << ")" << std::endl;
}
